Fresh Fixture
The book has now been published and the content of this chapter has likely changed substanstially.Please see page 311 of xUnit Test Patterns for the latest information.
Also known as: Fresh Context, Private Fixture
What fixture strategy should we use?
Each test constructs its own brand-new test fixture for its own private use.
Sketch Fresh Fixture embedded from Fresh Fixture.gif
Every test needs a test fixture. This defines the state of the test environment before the test. The choice of whether to build the fixture from scratch each time the test is run or to reuse a fixture built earlier is a key test automation decision.
When each test creates a Fresh Fixture, it prevents Erratic Tests (page X) and is more likely to result in Tests as Documentation (see Goals of Test Automation on page X).
How It Works
We design and build the test fixture such that only a single running of a single test will use it. We construct the fixture as part of running the test and tear down the fixture when the test has finished. We do not reuse any fixture left over by other tests or other test runs. This way, we start and end every test with a "clean slate".
When To Use It
We should use a Fresh Fixture whenever we want to avoid any interdependencies between tests that can result in Erratic Tests such as Lonely Test (see Erratic Test) or Interacting Tests (see Erratic Test). If we cannot use a Fresh Fixture because it slows the tests down too much, we should consider using an Immutable Shared Fixture (see Shared Fixture on page X) before resorting to a Shared Fixture. Note that using a Database Partitioning Scheme (see Database Sandbox on page X) to create a private Database Sandbox for the test that no other tests will touch does not result in a Fresh Fixture since subsequent test runs could use the same fixture.
Implementation Notes
A fixture is considered a Fresh Fixture if we intend to use it a single time. Whether the Fresh Fixture is transient or persistent depends on the nature of the system under test (SUT) and how we code the tests. While the intent is the same, the implementation considerations are somewhat different when the Fresh Fixture is persistent. Fixture set up is largely unaffected so it discussed in common. Fixture tear down is within the specific variations.
Why Does a Fixture Persist?
The fixture we construct may hang around after the Test Method (page X) has finished executing for one of two reasons. If the fixture is primarily the state of some other objects or components on which the SUT depends, the persistence of the fixture is determined by whether those other objects are themselves persistent. A database is one such beast. As soon as anything persists the fixture objects into a database the objects "hang around" long after our test is done. This opens the door to collisions between multiple runs of our own test (Unrepeatable Test (see Erratic Test).) As well, other tests may be able to access them, and that can result in other forms of Erratic Tests such as Interacting Tests and Test Run War. If we must use a database or other form of object persistence, we will need to take extra steps to keep the fixture private. We should tear down the fixture after each Test Method.
The second reason for fixtures to be persistence is within the control of our tests; the choice of what kind of variable we use to hold the reference to it. Local variables naturally go out of scope when the Test Method finishes executing therefore any fixture held in a local variable will be destroyed by garbage collection. Instance variables are usually (Most members of the xUnit family create a separate Testcase Object (page X) for each Test Method but a few do not and this can catch the unwary when they first start using these members of the family because instance variables unexpectedly act like class variables. For a detailed description of the issue see the sidebar There's Always an Exception (page X).) safe and should not normally require explicit tear down. The use of class variables usually results in persistent fixtures and should therefore be avoiding when using a Fresh Fixture.
In practice, our fixture will not normally be persistent in unit tests(See the sidebar Unit Test Rulz (page X) for what constitutes a unit test.) unless we have tightly coupled our application logic to the database; it is more likely to be persistent when writing customer tests or possibly component tests.
Sketch Test Fixture Strategies embedded from Test Fixture Strategies.gif
Fresh Fixture Setup
Construction of the fixture is largely unaffected by whether it is persistent or transient. The primary consideration is where we put the code to set up the fixture. We can use Inline Setup (page X) if the fixture set up is relatively simple. For more complex fixtures we are usually better off using Delegated Setup (page X) set up fixtures when our Test Methods are organized using Testcase Class per Class (page X) or Testcase Class per Feature (page X). We can use Implicit Setup (page X) to build the fixture if we have used the Testcase Class per Fixture (page X) organization.
Variation: Transient Fresh Fixture
If we need to refer to the fixture from several places in the test, we should use only local variables or instance variables to refer to the fixture. In most cases we can depend on Garbage-Collected Teardown (page X) to destroy the fixture for us without any effort whatsoever on our part.
Note that a Standard Fixture (page X) can also be a Fresh Fixture if the fixture is built from scratch before each Test Method is run. It merely reuses the design of the fixture rather than the instance. This commonly happens when we use either Implicit Setup and we haven't used Testcase Class per Fixture to organize our Test Methods.
Variation: Persistent Fresh Fixture
If we do end up using a Persistent Fresh Fixture, we will either need to tear down the fixture or take special measures to avoid needing to tear it down. We can tear down the fixture using one of Inline Teardown (page X), Implicit Teardown (page X), Delegated Teardown (see Inline Teardown) or Automated Teardown (page X) to leave the test environment in the same state as when we entered it.
We can avoid the need to tear down the fixture by using a Distinct Generated Value (see Generated Value on page X) for each fixture object that must be unique. This can become the basis of a Database Partitioning Scheme to isolate the tests and test runners from each other. This would prevent Resource Leakage (see Erratic Test) in case our tear down fails. We can also combine this approach with one of the tear down patterns to be doubly sure that we don't have Unrepeatable Tests or Interacting Tests.
All this additional work has two drawbacks: It makes tests more complicated to write and it often leads to Slow Tests (page X). A natural reaction is to take advantage of the persistence of the fixture by reusing it across many tests thus avoiding the overhead of setting it up and tearing it down. Unfortunately, this has many ramifications because it violates one of our principles: Keep Tests Independent (see Principles of Test Automation on page X). The resulting Shared Fixture invariably leads to Interacting Tests and Unrepeatable Tests, if not immediately, at some time down the road. We should not go down this road without fully understanding the consequences!
Motivating Example
Here's an example of a Shared Fixture:
static Flight flight; public void setUp() { if (flight == null) { // Lazy SetUp Airport departAirport = new Airport("Calgary", "YYC"); Airport destAirport = new Airport("Toronto", "YYZ"); flight = new Flight( flightNumber, departAirport, destAirport); } } public void testGetStatus_inital_S() { // implicit setup // Exercise SUT & verify outcome assertEquals(FlightState.PROPOSED, flight.getStatus()); // teardown: } public void testGetStatus_cancelled() { // implicit setup partially overriden: flight.cancel(); // exercise SUT & verify outcome assertEquals(FlightState.CANCELLED, flight.getStatus()); // teardown: } Example SharedTestFixture-FlightState embedded from java/com/clrstream/ex6/services/test/SetupStyles.java
The code that actually sets up the fixture is not shown because it could be either a normal Shared Fixture or a Prebuilt Fixture (page X). Either way, these tests could start interacting at any time.
Refactoring Notes
If we find ourself using a Shared Fixture (same design, single copy) and need to refactor to a Fresh Fixture, we can start by first refactoring to a fresh Standard Fixture (same design, many copies). Then we can decide whether we want to further evolve the tests to build a Minimal Fixture (page X) by pruning the fixture set up logic to the bare minimum using a Minimize Data (page X) refactoring. This would also be good time to group Test Methods that need the same type of test fixture into a Testcase Class per Fixture and use Implicit Setup; this use of Standard Fixture would reduce the number of Minimal Fixtures we need to design and build.
Example: Fresh Fixture
Here's the same test converted to Fresh Fixture to avoid any possibility of Interacting Tests:
public void testGetStatus_inital() { // setup: Flight flight = createAnonymousFlight(); // Exercise SUT & verify outcome: assertEquals(FlightState.PROPOSED, flight.getStatus()); // tearDown: // garbage-collected } public void testGetStatus_cancelled2() { // setup: Flight flight = createAnonymousCancelledFlight(); // Exercise SUT & verify outcome: assertEquals(FlightState.CANCELLED, flight.getStatus()); // tearDown: // garbage-collected } Example PrivateTestFixture embedded from java/com/clrstream/ex6/services/test/SetupStyles.java
Note the use of Anonymous Creation Methods (see Creation Method on page X) to construct the appropriate state Flight object in each test.
Copyright © 2003-2008 Gerard Meszaros all rights reserved